package com.soccertgc.logging
{
	import com.soccertgc.utils.ClassUtils;
	import com.soccertgc.utils.DateUtils;
	import com.soccertgc.utils.StringUtils;
	
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	
	// import mx.collections.IList;
	// import mx.utils.StringUtil;

	public class Dumper
	{

		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------		
		
		// Indenting constants
		public static const		INDENT_CHAR:String					= " ";
		public static const		INDENT_LENGTH:int					= 3;
		
		// Dump text constants
		public static const		DUMP_PAD_CHAR:String				= ":";
		public static const		DUMP_PAD_HEADER:String				= "::: ";
		public static const		DUMP_PAD_LENGTH:int					= 85;
		public static const		DUMP_PROP_INDENT:String				= "";

		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * 
		 * @param propName
		 * @param propType
		 * @param propValue
		 * @param indent
		 * @param source
		 */		
		private static function dumpValue(propName:String, propType:String, propValue:Object, indent:String, source:Array):void
		{
			var value:String = "";
			if (propValue is String)
			{
				value = "\"" + propValue.toString() + "\"";
			}
			else if (propValue is Date)
			{
				value = DateUtils.format(propValue as Date);
			}
			else
			{
				value = propValue.toString();
			}
			// Add line value to source
			source.push(format(indent + DUMP_PROP_INDENT + propName + ": " + value));
		}
		
		/**
		 * 
		 * @param propName
		 * @param propValue
		 * @param source
		 * @param indentDepth 
		 */		
		private static function dumpProperty(propName:String, propType:String, propValue:Object, source:Array, indentDepth:int):void
		{
			// Header based on indent
			var indent:String = Dumper.indent(indentDepth);
			// Check if the property exists
			if (propValue != null)
			{
				// Check if the property is dumpable
				if (propValue is IDumpable)
				{
					source.push(format(indent + DUMP_PROP_INDENT + propName + " <" + propType + "> "));
					// Executes the dump
					source = (propValue as IDumpable).dump(source, indentDepth + 1);
				}
				// Check if the property is an array
				else if (propValue is Array)
				{
					// Cast as array
					var a:Array = propValue as Array;
					// Prop title
					source.push(format(indent + DUMP_PROP_INDENT + propName + "  [length:" + a.length + "] "));
					// Loop over array items
					for (var i:int = 0; i < a.length; i++)
					{
						dumpProperty("[" + i + "]", getQualifiedClassName(a[i]), a[i], source, indentDepth + 1);
					}
				} 
				// Check if the property is a Vector
				// else if (propValue is Vector)
				else if (ClassUtils.isVector(propValue))
				{
					// Prop title
					source.push(format(indent + DUMP_PROP_INDENT + propName + " [length:" + propValue.length + "] "));
					// Loop over vector items
					for (var j:int = 0; j < propValue.length; j++)
					{
						dumpProperty("[" + j + "]", getQualifiedClassName(propValue[j]), propValue[j], source, indentDepth + 1);
					}
				}
				/*
				// Check if the property is a IList
				else if (propValue is IList)
				{
					// Cast as Vector
					var l:IList = propValue as IList;
					// Prop title
					source.push(format(indent + DUMP_PROP_INDENT + propName + " [length:" + l.length + "] "));
					// Loop over vector items
					for (var k:int = 0; k < l.length; k++)
					{
						var item:Object = l.getItemAt(k);
						dumpProperty("[" + k + "]", getQualifiedClassName(item), item, source, indentDepth + 1);
					}
				}
				*/
				else
				{
					// formats the property value
					dumpValue(propName, propType, propValue, indent, source);
				}
			}
		}
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * Returns a string for indenting a log entry by the given indent level 
		 * @param indentLevel
		 * @return 
		 */		
		public static function indent(indentLevel:int = 0):String
		{
			return StringUtils.repeat(INDENT_CHAR, INDENT_LENGTH * indentLevel);
		}		
		
		/**
		 *  
		 * @param message
		 * @param pad
		 * @return 
		 */		
		public static function format(message:String = "", pad:Boolean = false):String
		{
			var separator:String = StringUtils.isEmpty(message) ? "" : " ";
			return pad	? StringUtils.padRight(DUMP_PAD_HEADER + message + separator, DUMP_PAD_CHAR,  DUMP_PAD_LENGTH) 
						: DUMP_PAD_HEADER + message;
		}
		
		/**
		 * Standard dump behavour for IDumpable classes
		 * @param obj
		 * @param name
		 * @return 
		 */		
		public static function dump(obj:IDumpable, title:String = null, source:Array = null, indentDepth:int = 0, excludedProperties:Array = null):Array
		{
			// Locals
			var accessor:XML;
			var propName:String;
			var propType:String;
			var propValue:Object;
			// Object class metadata
			var metadata:XML = describeType(obj);
			// Name, if not provided, class name is used
			var tit:String = StringUtils.isEmpty(title) ? metadata.@name : title;
			// If no source is provided, perform a full dump with text decorators and additional infos
			var full:Boolean = source == null || (source != null && source.length == 0);
			// Check if a source is provided, otherwise creates a new array
			if (source == null) source = new Array();
			// Add header in dump 
			if (full)
			{
				source.push("", true);
				source.push(Dumper.format("DUMP " + tit, true));
			}
			// Retrieve the object accessors
			var accessors:XMLList = metadata.accessor; 
			// Loop over attributes
			for each (accessor in accessors) 
			{
				// Get the property data
				propName = accessor.@name.toString();
				propType = accessor.@type.toString();
				propValue = obj[propName];
				// Check if the property is exclueded from the dump
				if (excludedProperties == null || excludedProperties.indexOf(propName) == -1)
				{
					// Dumps the property
					dumpProperty(propName, propType, propValue, source, indentDepth);	
				}
			}
			// Add footer in dump 
			if (full)
			{
				source.push(Dumper.format("DUMP " + tit, true));
				source.push("", true);
			}
			// Returns the dump
			return source;
		}
		
		
	}
}